package com.syncomponents.context;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.ApplicationObjectSupport;

import com.syncomponents.context.annotation.Action;
import com.syncomponents.context.annotation.ClientComponent;
import com.syncomponents.context.annotation.SynComponent;
import com.syncomponents.context.annotation.SynHandler;
import com.syncomponents.context.support.MessageHandler;
import com.syncomponents.core.NotSupportedParamterException;
import com.syncomponents.core.RegisterException;
import com.syncomponents.message.BaseMessage;
import com.syncomponents.remoting.ClientFactory;
import com.syncomponents.remoting.IClient;
import com.syncomponents.util.ReflectionUtils;

public class SynApplicationContext extends ApplicationObjectSupport implements InitializingBean {

    private ComponentFactory componentFactory;

    private HandlerFactory handlerFactory;

    private ClientFactory clientFactory;

    public SynApplicationContext(ComponentFactory componentFactory, HandlerFactory handlerFactory) {
        this.componentFactory = componentFactory;
        this.handlerFactory = handlerFactory;
    }

    public SynApplicationContext(ComponentFactory componentFactory, HandlerFactory handlerFactory,
            ClientFactory clientFactory) {
        this.componentFactory = componentFactory;
        this.handlerFactory = handlerFactory;
        this.clientFactory = clientFactory;
    }

    private static final Logger logger = LoggerFactory.getLogger(SynApplicationContext.class);

    @Override
    public void afterPropertiesSet() throws Exception {
        init();
    }

    protected void init() {
        logger.info("Syn Application is initiating");

        initComponent();

        initHandler();

        initClient();

        logger.info("Syn Application has been finished");
    }

    private void initComponent() {
        Map<String, Object> componentMap = getApplicationContext().getBeansWithAnnotation(SynComponent.class);

        // TODO 抽取方法
        Set<String> names = new HashSet<String>();

        for (Object obj : componentMap.values()) {
            Class<?> clazz = obj.getClass();

            SynComponent synComponent = clazz.getAnnotation(SynComponent.class);

            names.add(synComponent.type());

            Method[] methods = ReflectionUtils.findMethod(obj, Action.class);
            if (methods == null || methods.length == 0) {
                continue;
            }

            for (Method method : methods) {

                Action action = method.getAnnotation(Action.class);

                String key = synComponent.type() + "." + action.value();

                if (componentFactory.contains(key)) {
                    throw new RegisterException(key + " is duplicate.");
                }

                componentFactory.register(key, getActionEntity(obj, method));

            }

        }
        logger.info("component size is " + componentFactory.getSize());
    }

    private void initHandler() {
        Map<String, Object> handlerMap = getApplicationContext().getBeansWithAnnotation(SynHandler.class);

        for (Object obj : handlerMap.values()) {
            Class<?> clazz = obj.getClass();

            SynHandler synHandler = clazz.getAnnotation(SynHandler.class);

            if (!(obj instanceof MessageHandler)) {
                throw new RegisterException(clazz.getName()
                        + " must be implement com.syncomponents.context.support.MessageHandler");
            }

            handlerFactory.register(synHandler.type().getValue(), (MessageHandler) obj);

        }

        logger.info("handler size is " + handlerFactory.getSize());
    }

    @SuppressWarnings("rawtypes")
    private void initClient() {
        if (clientFactory == null) {
            return;
        }
        Map<String, Object> handlerMap = getApplicationContext().getBeansWithAnnotation(ClientComponent.class);

        for (Object obj : handlerMap.values()) {
            Class<?> clazz = obj.getClass();

            ClientComponent clientComponent = clazz.getAnnotation(ClientComponent.class);

            if (!(obj instanceof IClient)) {
                throw new RegisterException(clazz.getName() + " must be implement IClient");
            }

            clientFactory.register(clientComponent.type(), (IClient) obj);

        }

        logger.info("client size is " + clientFactory.getSize());
    }

    /**
     * 
     * @param requiredType type the bean must match. Can be an interface or superclass
     *            of the actual class, or {@code null} for any match. For example, if the value
     *            is {@code Object.class}, this method will succeed whatever the class of the
     *            returned instance.
     * @param method the method of the bean to retrieve
     * @return
     */
    private ActionEntity getActionEntity(Object obj, Method method) {
        ActionEntity actionEntity = new ActionEntity(obj, method);

        Class<?>[] paramTypes = method.getParameterTypes();
        if (paramTypes != null) {
            if (paramTypes.length > 1) {
                throw new NotSupportedParamterException("only support one argument");
            }
            if (!BaseMessage.class.isAssignableFrom(paramTypes[0])) {
                throw new NotSupportedParamterException(paramTypes[0]
                        + " must be implement com.syncomponents.message.SynMessage");
            }
            actionEntity.setParamTypes(paramTypes);
        }

        return actionEntity;
    }

    public ComponentFactory getComponentFactory() {
        return componentFactory;
    }

    public HandlerFactory getHandlerFactory() {
        return handlerFactory;
    }

    public ClientFactory getClientFactory() {
        return clientFactory;
    }

    public void setClientFactory(ClientFactory clientFactory) {
        this.clientFactory = clientFactory;
    }

}
